/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.kramerius.articles.importFromLn.articlesData.jiranova;

import cz.mzk.kramerius.articles.importFromLn.articlesData.ArticleComponentsCreator;
import cz.mzk.kramerius.articles.importFromLn.articlesData.errors.ACErrorType;
import cz.mzk.kramerius.articles.importFromLn.articlesData.errors.ArticleError;
import cz.mzk.kramerius.articles.importFromLn.articlesData.errors.ErrorFactory;
import cz.mzk.kramerius.articles.importFromLn.domain.ArticleComponent;
import cz.mzk.kramerius.articles.importFromLn.domain.DayTime;
import cz.mzk.kramerius.articles.importFromLn.domain.Issue;
import cz.mzk.kramerius.articles.importFromLn.domain.NumberInterval;
import cz.mzk.kramerius.articles.importFromLn.krameriusAccess.Repository;
import cz.mzk.kramerius.commons.PropertyLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.joda.time.IllegalFieldValueException;
import org.joda.time.LocalDate;

/**
 * This class creates article components provided they are all from single volumeNumber. I.e. the
 * article was issued in single volumeNumber
 *
 * @author Martin Řehánek (rehan@mzk.cz)
 */
public class SingleVolumeACCreator implements ArticleComponentsCreator {

    private static final Logger logger = Logger.getLogger(SingleVolumeACCreator.class.getName());
    private final PropertyLoader properties;
    private final InputData inputData;
    private Repository repository;
    //results of processing input data
    private int volumeNumber;
    private String volumePid;
    private List<LocalDate> dateList;
    private List<Integer> issueNumberList;
    private List<DayTime> dayTimeList;
    private List<NumberInterval> pageIntervals;
    //total result
    private final List<ArticleComponent> articleComponents = new ArrayList<ArticleComponent>();
    private ArticleError error = null;

    /**
     *
     * @param properties
     * @param volumesString
     * @param datesString
     * @param issueNumbersString
     * @param dateTimesString
     * @param pagesString
     * @param articleId
     */
    public SingleVolumeACCreator(Properties properties, String volumesString, String datesString, String issueNumbersString, String dateTimesString, String pagesString, int articleId) {
        this.properties = new PropertyLoader(properties);
        this.inputData = new InputData(volumesString, datesString, issueNumbersString, dateTimesString, pagesString, articleId);
        initArticleComponents();
    }

    @Override
    public List<ArticleComponent> getArticleComponents() {
        return articleComponents;
    }

    private boolean parseData() {
        if (!parseVolume()) {
            setError(ACErrorType.errorParsing, new Object[]{"číslo ročníku"});
            return false;
        }
        if (!parseDates()) {
            setError(ACErrorType.errorParsing, new Object[]{"datum"});
            return false;
        }
        if (!parseIssueNumbers()) {
            setError(ACErrorType.errorParsing, new Object[]{"číslo vydání"});
            return false;
        }
        if (!parseDayTimes()) {
            setError(ACErrorType.errorParsing, new Object[]{"druh vydání"});
            return false;
        }
        if (!parsePages()) {
            setError(ACErrorType.errorParsing, new Object[]{"číslo strany"});
            return false;
        }
        return true;
    }

    private void initArticleComponents() {
        if (parseData()) {
            multipleDateTimes();
            int dateRecords = dateList.size();
            int issueNumberRecords = issueNumberList.size();
            int pageRecords = pageIntervals.size();
            int dayTimeRecords = dayTimeList.size();
            if (!checkVolumeExists()) {//jestli rocnik opravdu existuje v repository
                setError(ACErrorType.errorVolumeMissing, null);
                return;
            } else if (!checkDatesOrder()) {//jestli se datumy nepredbihaji
                setError(ACErrorType.errorDatesNotInAscendingOrder, null);
                return;
            } else if (issueNumberRecords != dayTimeRecords) {//test na pocet cisel vydani vs pocet dayTime
                setError(ACErrorType.errorIssueNumbersNotEqualsDayTimes, new Object[]{Integer.valueOf(issueNumberRecords), Integer.valueOf(dayTimeRecords)});
                return;
            } else if (dateRecords != issueNumberRecords) {//test na pocet datumu vs pocet cisel vydani
                setError(ACErrorType.errorIssueNumbersNotEqualsDayTimes, new Object[]{Integer.valueOf(dateRecords), Integer.valueOf(issueNumberRecords)});
                return;
            } else if (!checkIssuesExist()) {//test na existenci vsech vydani
                setError(ACErrorType.errorIssueMissing, null);
                return;
            } else if (dateRecords != pageRecords) {//test na pocet datumu vs pocet intervalu stran
                setError(ACErrorType.errorIssueNumbersNotEqualsDayTimes, new Object[]{Integer.valueOf(dateRecords), Integer.valueOf(pageRecords)});
                return;
            } else if (!checkDateNumberMappings()) {//test jestli sedi datumy a cisla
                setError(ACErrorType.incorrectDateNumberMapping, null);
                return;
            } else {//no errors found
                for (int i = 0; i < dateRecords; i++) {
                    Issue issue = Issue.instanceOf(issueNumberList.get(i), dateList.get(i), dayTimeList.get(i), false, 0);
                    ArticleComponent articleComponent = ArticleComponent.instanceOf(volumeNumber, issue, pageIntervals.get(i));
                    articleComponents.add(articleComponent);
                }
            }
        }
    }

    private LocalDate string2LocalDate(String string) {
        if (string.length() != 8) {
            logger.log(Level.SEVERE, "Wrong format of date ''{0}'', articleId: {1}", new Object[]{string, inputData.getArticleId()});
            return null;
        }
        try {
            String yearStr = string.substring(0, 4);
            int year = Integer.parseInt(yearStr);
            String monthStr = string.substring(4, 6);
            int month = Integer.parseInt(monthStr);
            String dayStr = string.substring(6, 8);
            int day = Integer.parseInt(dayStr);
            return new LocalDate(year, month, day);
        } catch (NumberFormatException ex) {
            logger.log(Level.SEVERE, "Wrong format of date ''{0}'', articleId: {1}", new Object[]{string, inputData.getArticleId()});
            return null;
        } catch (IllegalFieldValueException ex) {
            String message = "date: '" + string + "', articleId: " + inputData.getArticleId();
            logger.log(Level.SEVERE, message, ex);
            return null;
        }
    }

    private boolean parseVolume() {
        try {
            volumeNumber = Integer.valueOf(inputData.getVolumesString());
            volumePid = getRepository().getVolumePid(volumeNumber);
            return true;
        } catch (NumberFormatException ex) {
            logger.log(Level.SEVERE, "Wrong format of volume ''{0}'', articleId: {1}", new Object[]{inputData.getVolumesString(), inputData.getArticleId()});
            return false;
        }
    }

    private boolean checkVolumeExists() {
        return volumePid != null;
    }

    private boolean parseDates() {
        String[] datesStr = inputData.getDatesString().split(",");
        dateList = new ArrayList<LocalDate>(datesStr.length);
        for (int i = 0; i < datesStr.length; i++) {
            LocalDate date = string2LocalDate(datesStr[i]);
            if (date != null) {
                dateList.add(date);
            } else {
                return false;
            }
        }
        return true;
    }

    private boolean checkDatesOrder() {
        //kontrola, jestli se datumy nepredbihaji. V seznamu maji byt usporadany vzestupne
        LocalDate previous = new LocalDate(1800, 1, 1);
        for (LocalDate date : dateList) {
            if (date == null) {
                logger.log(Level.SEVERE, "date == null, articleId:{0}", inputData.getArticleId());
                return false;
            } else {
                if (!date.isAfter(previous)) {
                    logger.log(Level.SEVERE, "date {0} is not after {1}, articleId: {2}", new Object[]{date, previous, inputData.getArticleId()});

                    return false;
                } else {
                    previous = date;
                }
            }
        }
        return true;
    }

    private boolean parseIssueNumbers() {
        String[] numbersStr = inputData.getIssueNumbersString().split(",");
        issueNumberList = new ArrayList<Integer>(numbersStr.length);
        for (int i = 0; i < numbersStr.length; i++) {
            String issueStr = numbersStr[i];
            try {
                Integer number = Integer.valueOf(issueStr);
                issueNumberList.add(number);
            } catch (NumberFormatException ex) {
                logger.log(Level.SEVERE, "Wrong format of issue number ''{0}'', articleId: {1}", new Object[]{issueStr, inputData.getArticleId()});
                return false;
            }
        }
        return true;
    }

    private boolean parseDayTimes() {
        String[] dayTimesStr = inputData.getDayTimesString().split(",");
        dayTimeList = new ArrayList<DayTime>(dayTimesStr.length);
        for (int i = 0; i < dayTimesStr.length; i++) {
            DayTime dayTime = DayTime.parseString(dayTimesStr[i]);
            dayTimeList.add(dayTime);
            /* if (dayTime == DayTime.unspecified) {
             return false;
             }*/
        }
        return true;
    }

    /**
     * This method requires that issueNumberList.size() == dayTimeList.size() == dateList.size()
     *
     * @return true if there exist an issue in repository for given issueNumber, dayTime and date
     */
    private boolean checkIssuesExist() {
        for (int i = 0; i < issueNumberList.size(); i++) {
            Integer issueNumber = issueNumberList.get(i);
            DayTime dayTime = dayTimeList.get(i);
            LocalDate date = dateList.get(i);
            Issue issue = Issue.instanceOf(issueNumber, date, dayTime, false, 0);
            if (issue == null) {//not handeled error in input data (issueNumber, date or dayTime probably null)
                logger.log(Level.SEVERE, "issue == null");
                return false;
            }
            String issuePid = getRepository().getIssuePid(volumePid, volumeNumber, issue);
            if (issuePid == null) {//no such an issue in repository
                return false;
            }
        }
        return true;
    }

    private boolean parsePages() {
        pageIntervals = new ArrayList<NumberInterval>();
        String[] parts = inputData.getPagesString().split(",");
        String pageInterval;
        for (int i = 0; i < parts.length; i++) {
            pageInterval = parts[i];
            String[] intervalParts = pageInterval.split("-");
            if (intervalParts.length == 1) { //tj. '-' se nevyskytuje a jde o jednu stranu
                try {
                    Integer number = Integer.valueOf(intervalParts[0]);
                    pageIntervals.add(new NumberInterval(number, number));
                } catch (NumberFormatException ex) {
                    logger.log(Level.SEVERE, "Wrong format of page ''{0}'', articleId: {1}", new Object[]{intervalParts[0], inputData.getArticleId()});
                    return false;
                }
            } else if (intervalParts.length == 2) { //udaj typu "10-12"
                try {
                    Integer start = Integer.valueOf(intervalParts[0]);
                    Integer end = Integer.valueOf(intervalParts[1]);
                    pageIntervals.add(new NumberInterval(start, end));
                } catch (NumberFormatException ex) {
                    logger.log(Level.SEVERE, "Wrong format of pages ''{0}'', articleId: {1}", new Object[]{pageInterval, inputData.getArticleId()});
                    return false;
                }
            } else {
                logger.log(Level.SEVERE, "Wrong format of pages ''{0}'', articleId: {1}", new Object[]{pageInterval, inputData.getArticleId()});
                return false;
            }
        }
        return true;
    }

    private void multipleDateTimes() {
        if ((dayTimeList.size() == 1) && (dateList.size() > 1)) {
            DayTime dt = dayTimeList.get(0);
            for (int i = 0; i < dateList.size() - 1; i++) {
                dayTimeList.add(dt);
            }
        }
    }

    /**
     *
     * @return true if all issue numbers fit to appropriate dates
     */
    private boolean checkDateNumberMappings() {
        for (int i = 0; i < dateList.size(); i++) {
            if (!fits(issueNumberList.get(i), dateList.get(i))) {
                return false;
            }
        }
        return true;
    }

    private boolean fits(int issueNumber, LocalDate date) {
        Repository rep = getRepository();
        LocalDate repDate = rep.getIssueDate(volumeNumber, issueNumber);
        return date.equals(repDate);
    }

    private Repository getRepository() {
        if (repository == null) {
            repository = Repository.instanceOf(properties);
        }
        return repository;
    }

    @Override
    public ArticleError getError() {
        return error;
    }

    private void setError(ACErrorType errorType, Object[] params) {
        error = ErrorFactory.createACError(errorType, params);
    }

    /**
     * Inner class that groups input data of all article parts
     */
    private class InputData {

        private final String volumesString;
        private final String datesString;
        private final String issueNumbersString;
        private final String dayTimesString;
        private final String pagesString;
        private final int articleId;

        public InputData(String volumesString, String datesString, String issueNumbersString, String dayTimesString, String pagesString, int articleId) {

            this.volumesString = volumesString;
            this.datesString = datesString;
            this.issueNumbersString = issueNumbersString;
            this.dayTimesString = dayTimesString;
            this.pagesString = pagesString;
            this.articleId = articleId;
        }

        /**
         * @return the volumes
         */
        public String getVolumesString() {
            return volumesString;
        }

        /**
         * @return the dates
         */
        public String getDatesString() {
            return datesString;
        }

        /**
         * @return the issues
         */
        public String getIssueNumbersString() {
            return issueNumbersString;
        }

        /**
         * @return the dayTimes
         */
        public String getDayTimesString() {
            return dayTimesString;
        }

        /**
         * @return the pages
         */
        public String getPagesString() {
            return pagesString;
        }

        /**
         * @return the articleId
         */
        public int getArticleId() {
            return articleId;
        }
    }
}
